package com.zhang.hadoop.flink.test8;

import com.zhang.hadoop.flink.base.ClickSource;
import com.zhang.hadoop.flink.base.Event;
import org.apache.flink.api.common.eventtime.SerializableTimestampAssigner;
import org.apache.flink.api.common.eventtime.WatermarkStrategy;
import org.apache.flink.api.common.state.MapState;
import org.apache.flink.api.common.state.MapStateDescriptor;
import org.apache.flink.configuration.Configuration;
import org.apache.flink.streaming.api.datastream.SingleOutputStreamOperator;
import org.apache.flink.streaming.api.environment.StreamExecutionEnvironment;
import org.apache.flink.streaming.api.functions.KeyedProcessFunction;
import org.apache.flink.util.Collector;

import java.time.Duration;
import java.time.Instant;

/**
 * @author: zhang yufei
 * @createTime:2022/8/13 16:15
 * @description:
 */
public class FakeWindowExample {

    public static void main(String[] args) throws Exception {
        StreamExecutionEnvironment env = StreamExecutionEnvironment.getExecutionEnvironment();
        env.setParallelism(1);

        SingleOutputStreamOperator<Event> stream = env.addSource(new ClickSource())
                .assignTimestampsAndWatermarks(WatermarkStrategy.<Event>forBoundedOutOfOrderness(Duration.ZERO)
                        .withTimestampAssigner(new SerializableTimestampAssigner<Event>() {
                            @Override
                            public long extractTimestamp(Event event, long recordTimestamp) {
                                return event.timestamp;
                            }
                        }));

        stream.print("input:");

        stream.keyBy(data -> data.user)
                .process(new FakeWindowResult(10000L))
                .print();

        env.execute();
    }

    //实现自定义的keyedProcessFunction
    public static class FakeWindowResult extends KeyedProcessFunction<String, Event, String> {

        //窗口大小
        private Long windowSize;

        //定义一个MapState，用来保存每个窗口中统计的count值
        MapState<Long, Long> windowUrlCountMapState;

        public FakeWindowResult(Long windowSize) {
            this.windowSize = windowSize;
        }

        @Override
        public void open(Configuration parameters) throws Exception {
            windowUrlCountMapState = getRuntimeContext().getMapState(new MapStateDescriptor<Long, Long>("window-count", Long.class, Long.class));
        }

        @Override
        public void processElement(Event event, Context context, Collector<String> collector) throws Exception {
            //每来一条数据，根据时间戳判断属于南哥窗口（窗口分配器）
            Long windowStart = event.timestamp / windowSize * windowSize;
            Long windowEnd = windowStart + windowSize;

            //注册end-1的定时器
            context.timerService().registerEventTimeTimer(windowEnd - 1);

            //更新状态，进行增量聚合
            if (windowUrlCountMapState.contains(windowStart)) {
                Long count = windowUrlCountMapState.get(windowStart);
                windowUrlCountMapState.put(windowStart, count + 1L);
            } else {
                windowUrlCountMapState.put(windowStart, 1L);
            }
        }

        @Override
        public void onTimer(long timestamp, OnTimerContext ctx, Collector<String> out) throws Exception {
            Long windowEnd = timestamp + 1;
            Long windowStart = windowEnd - windowSize;
            Long count = windowUrlCountMapState.get(windowStart);

            out.collect("窗口" + Instant.ofEpochMilli(windowStart) + "  " + Instant.ofEpochMilli(windowEnd)
                    + " url:" + ctx.getCurrentKey()
                    + " count:" + count);

            //模拟窗口的关闭，清除map中对应的key-value
            windowUrlCountMapState.remove(windowStart);
        }
    }
}
